package compilers.AST.basics;

import java.util.Iterator;
import java.util.LinkedList;

import compilers.AST.basics.Identifier;
import compilers.AST.expression.Expression;
import compilers.AST.expression.Term;
import compilers.AST.types.MyType;
import compilers.AST.types.TypeSpecifier;
import compilers.compiler.PGenerator;
import compilers.exceptions.SemanticalException;
import compilers.symbolTable.SymbolTableArrayElement;
import compilers.symbolTable.SymbolTableFunctionElement;
import compilers.symbolTable.SymbolTableScope;

public class FunctionCallTerm extends Term {
	
	private Identifier fIdentifier;
	private LinkedList<Expression> fParameters;
	private TypeSpecifier fConvertTo = null; 

	public FunctionCallTerm() {
		fParameters = new LinkedList<Expression>();
	}
	
	public FunctionCallTerm(int lineNr) {
		super(lineNr);
		fParameters = new LinkedList<Expression>();
	}
	
	public void print(int identLevel) {
		println(identLevel, "FunctionCallTerm");
		fIdentifier.print(identLevel+1);
		Iterator<Expression> it = fParameters.iterator();
		while (it.hasNext()) {
			it.next().print(identLevel+1);
		}
	}
	
	public void setIdentifier(Identifier ident) {
		fIdentifier = ident;
	}
	
	public Identifier getIdentifier() {
		return fIdentifier;
	}
	
	public void addParameter(Expression expression) {
		fParameters.add(expression);
	}
	
	public int getOriginalType(SymbolTableScope currentScope) {
		SymbolTableFunctionElement funElem = (SymbolTableFunctionElement) currentScope.getElement(fIdentifier.getValue(), fNodeId);
		if(funElem != null) {
			return funElem.getType().getType(currentScope);
		}
		return 0;
	}
	
	public int getResultType(SymbolTableScope currentScope) {
		SymbolTableFunctionElement funElem = (SymbolTableFunctionElement) currentScope.getElement(fIdentifier.getValue(), fNodeId);
		if(funElem != null) {
			if(this.fConvertTo != null) {
				return this.fConvertTo.getType(currentScope);
			}
			return funElem.getType().getType(currentScope);
		}
		return 0;
	}
	
	public void checkSemantics(SymbolTableScope currentScope) throws SemanticalException {
		SymbolTableFunctionElement funElem;
		try {
			funElem = (SymbolTableFunctionElement) currentScope.getElement(fIdentifier.getValue(), fNodeId);
		} catch(Exception e) {
			throw new SemanticalException("'" + fIdentifier.getValue() + "' is not a function.", getLine());
		}
		
		//Verify whether the function exists in the scope
		if(funElem == null) {
			throw new SemanticalException("Function '" + fIdentifier.getValue() + "' is not defined.", getLine());
		} else {
			//Check actual with formal parameters
			
			LinkedList<Parameter> formalParameters = funElem.getParameters(); 
			
			int nrOfActualParms = fParameters.size();
			int nrOfFormalParms = formalParameters.size();
			
			if(nrOfActualParms < nrOfFormalParms) {
				throw new SemanticalException("Not enough arguments given to call function '" + fIdentifier.getValue() + "'.", getLine());
			} else if(nrOfActualParms > nrOfFormalParms) {
				throw new SemanticalException("Too many arguments given to call function '" + fIdentifier.getValue() + "'.", getLine());
			} else {
				Iterator<Parameter> formParmIt = formalParameters.iterator();
				Iterator<Expression> actualParmIt = fParameters.iterator();
				
				while(formParmIt.hasNext()) {
					Expression actualParm = actualParmIt.next();
					actualParm.checkSemantics(currentScope);
					
					Parameter formParm = formParmIt.next();
					TypeSpecifier formParmType = formParm.getType();
					
					TypeSpecifier actualParmType = new TypeSpecifier(actualParm.getResultType(currentScope));
					
					if(formParmType.getType(currentScope) != actualParmType.getType(currentScope)) {
						throw new SemanticalException("Parameter of type '" + actualParmType.getTypeName() + "' doesn't match with type '" + formParmType.getTypeName() + "'.", getLine());
					}
				}
			}
		}
	}
	
	public boolean isConstant() {
		return false;
	}

	public boolean isConstant(SymbolTableScope currentScope) {
		return false;
	}

	public MyType calculateValue(SymbolTableScope currentScope) {
		return null;
	}
	
	public void generatePcode(PGenerator pGen, SymbolTableScope currentScope) {
		// Get function scope
		SymbolTableScope root = currentScope.getRoot();
		SymbolTableScope functionScope = root.findFunctionScope(this.fIdentifier.getValue());
		pGen.mst(functionScope.getDepth());
		
		// Place parameters on stack
		Iterator<Expression> params = fParameters.iterator();
		while (params.hasNext()) {
			Expression param = params.next();
			if (param.isConstant(currentScope)) {
				pGen.load(TypeSpecifier.getPType(param.getResultType(currentScope)), param.calculateValue(currentScope).valueToString());
			} else {
				param.generatePcode(pGen, currentScope);
			}
		}
		
		// Call function
		pGen.cup(fParameters.size(), "begin_" + fIdentifier.getValue());
		
		//Convert if necessary
		if(this.fConvertTo != null) {
			pGen.conv(TypeSpecifier.getPType(this.getOriginalType(currentScope)), this.fConvertTo.getPType(currentScope));
		}
	}
	
	public void setConvertTo(TypeSpecifier type) {
		this.fConvertTo = type;
	}
	
	public boolean isArray(SymbolTableScope currentScope) {
		return false;
	}
	
	public String getArrayIdent(SymbolTableScope currentScope) {
		return null;
	}
}
